Here we will map an example dataset from Roy et al (Cell Rep 2019) comprising 12,245 CD34+ cells from fetal and adult bone marrow. Note that any human hematopoietic cells, regardless of tissue source or disease status, can be mapped to this reference. This tutorial will take approximately 10 minutes to run.

Setup

library(Seurat)
library(tidyverse)
library(symphony)
library(ggpubr)
library(patchwork)

Install package from github

## install dependencies that are not on CRAN
if(!require(BiocManager, quietly = TRUE)) install.packages("BiocManager")
BiocManager::install(c("AUCell", "doMC"))
if(!require(devtools, quietly = TRUE)) install.packages("devtools")
devtools::install_github("jaredhuling/jcolors")
## install BoneMarrowMap package
devtools::install_github('andygxzeng/BoneMarrowMap', force = TRUE)
library(BoneMarrowMap)

Download reference object and UMAP model

Set projection folder, Download reference object and UMAP model

# Set directory to store projection reference files
projection_path = './'

# Download Bone Marrow Reference - 344 Mb
curl::curl_download('https://bonemarrowmap.s3.us-east-2.amazonaws.com/BoneMarrow_RefMap_SymphonyRef.rds', 
                    destfile = paste0(projection_path, 'BoneMarrow_RefMap_SymphonyRef.rds'))
# Download uwot model file - 221 Mb
curl::curl_download('https://bonemarrowmap.s3.us-east-2.amazonaws.com/BoneMarrow_RefMap_uwot_model.uwot', 
                    destfile = paste0(projection_path, 'BoneMarrow_RefMap_uwot_model.uwot'))

# Load Symphony reference
ref <- readRDS(paste0(projection_path, 'BoneMarrow_RefMap_SymphonyRef.rds'))
# Set uwot path for UMAP projection
ref$save_uwot_path <- paste0(projection_path, 'BoneMarrow_RefMap_uwot_model.uwot')

Visualize Bone Marrow Reference

If we want to visualize celltype labels or metadata from the BM Reference, we can create a Seurat Object from the symphony reference This will be memory efficient as it will not include gene expression counts, only the UMAP coordinates and the metadata including cell labels and sorting information

ReferenceSeuratObj <- create_ReferenceObject(ref)

DimPlot(ReferenceSeuratObj, reduction = 'umap', group.by = 'CellType_Annotation_formatted', 
        raster=FALSE, label=TRUE, label.size = 4)

We can visualize other annotations too, including cell cycle phase and lineage pseudotime estimates.

p1 <- DimPlot(ReferenceSeuratObj, reduction = 'umap', group.by = 'CyclePhase', raster=FALSE)
p2 <- FeaturePlot(ReferenceSeuratObj, reduction = 'umap', features = 'Pseudotime', raster=FALSE) 

p1 + p2

Load Query Seurat object

Example Query object is a subset of data from Roy et al (Cell Reports 2021), incorporating CD34p cells from an adult bone marrow sample and a fetal bone marrow sample. Here, we prefer raw count data without normalization.

Input does not need to be a seurat object, can load raw count matrix and metadata separately

# Load example data from Roy et al - 141 Mb
curl::curl_download('https://bonemarrowmap.s3.us-east-2.amazonaws.com/ExampleQuery_Roy2021.rds',
                    destfile = paste0(projection_path, 'ExampleQuery_Roy2021.rds'))

# Load seurat object
query <- readRDS(paste0(projection_path, 'ExampleQuery_Roy2021.rds'))
query
An object of class Seurat 
33538 features across 12245 samples within 1 assay 
Active assay: RNA (33538 features, 0 variable features)

Map the Query Data

Provide raw counts, metadata, and donor key. This should take <1 min Calculate mapping error and perform QC to remove low quality cells with high mapping error

# batch variable to correct in the query data, set as NULL if no batches in query
batchvar <- 'sampleID'

# Map query dataset using Symphony (Kang et al 2021)
query <- map_Query(
    exp_query = query@assays$RNA@counts, 
    metadata_query = query@meta.data,
    ref_obj = ref,
    vars = batchvar
)
Normalizing
Scaling and synchronizing query gene expression
Found 2386 reference variable genes in query dataset
Project query cells using reference gene loadings
Clustering query cells to reference centroids
Correcting query batch effects
UMAP
All done!
Warning: Invalid name supplied, making object name syntactically valid. New object name is Seurat..ProjectDim.RNA.harmony; see ?make.names for more details on syntax validity

Now that the data is mapped, we will evaluate the mapping QC metrics and flag cells with high mapping errors

# Run QC based on mapping error score, flag cells with mapping error >= 2.5 MADs above median
query <- query %>% calculate_MappingError(., reference = ref, MAD_threshold = 2.5) 

# Get QC Plots
QC_plots <- plot_MappingErrorQC(query)

# Plot together - If this is too crowded, can also just call "QC_plots" aloneto display one by one
patchwork::wrap_plots(QC_plots, ncol = 4, widths = c(0.8, 0.3, 0.8, 0.3))

This important step identifies a subset of cells with high mapping error from the query dataset that are either:

Sometimes, low quality cells may erroneously map to the orthochromatic erythroblast region as this cell type has very low transcriptional diversity. These low quality query cells do not have hemoglobin expression and are in fact mis-mapped; they will be flagged by the QC filter and excluded from cell type assignments.

Please adjust the MAD_threshold (typically between 1 and 3) based on the distribution of your dataset to identify the outliers with low quality and high mapping error scores. This will improve your classifications and any downstream composition analysis

# # Optional step - remove outliers with high mapping error
# query <- subset(query, mapping_error_QC == 'Pass')

Optionally, outlier cells with high mapping error can also be removed at this stage. For ease of integrating these mapped annotations with the rest of your analysis, we also choose to skip this step. If so, Final CellType and Pseudotime predictions will be assigned as NA for cells failing the mapping error QC threshold.

Cell Type Assignments

We will next use a KNN classifier to assign cell identity based on the 30 K-Nearest Neighbours from the reference map. This label transfer step will take longer, potentially around 10 minutes for ~10,000 cells

# Predict Hematopoietic Cell Types by KNN classification
query <- predict_CellTypes(
  query_obj = query, 
  ref_obj = ref, 
  initial_label = 'initial_CellType', # celltype assignments before filtering on mapping QC
  final_label = 'predicted_CellType'  # celltype assignments with map QC failing cells assigned as NA
) 

DimPlot(subset(query, mapping_error_QC == 'Pass'), reduction = 'umap', group.by = c('predicted_CellType'), raster=FALSE, label=TRUE, label.size = 4)

Pseudotime Annotations

We can also annotate each query cell based on their position along hematopoietic pseudotime. Query cells will be assigned a pseudotime score based on the 30 K-Nearest Neighbours from the reference map. Since our Pseudotime KNN assignments are performed in UMAP space (more accurate than KNN on harmony components), this step is very fast (< 10s)

# Predict Pseudotime values by KNN
query <- predict_Pseudotime(
  query_obj = query, 
  ref_obj = ref, 
  initial_label = 'initial_Pseudotime',  # pseudotime assignments before filtering on mapping QC
  final_label = 'predicted_Pseudotime'   # pseudotime assignments with map QC failing cells assigned as NA
)

# Visualize Hematopoietic Pseudotime in query data
FeaturePlot(subset(query, mapping_error_QC == 'Pass'), features = c('predicted_Pseudotime'))

Save projection results

This will save a csv file with the mapped annotations for each cell (mapping error scores, umap coordinates, predicted Cell Type, and predicted Pseudotime)

# Save CellType Annotations and Projected UMAP coordinates
save_ProjectionResults(
  query_obj = query, 
  celltype_label = 'predicted_CellType', 
  celltype_KNNprob_label = 'predicted_CellType_prob', 
  pseudotime_label = 'predicted_Pseudotime', 
  file_name = 'querydata_projected_labeled.csv')

Visualize Projection Density

Now let’s visualize the density distribution of query cells across the hematopoietic hierarchy

# Set batch/condition to be visualized individually
batch_key <- 'sampleID'

# returns a list of plots for each donor from a pre-specified batch variable
projection_plots <- plot_Projection_byDonor(
  query_obj = query, 
  batch_key = batch_key, 
  ref_obj = ref, 
  Hierarchy_only = FALSE, # Whether to exclude T/NK/Plasma/Stromal cells 
  downsample_reference = TRUE, 
  downsample_frac = 0.25,   # down-sample reference cells to 25%; reduces figure file size
  query_point_size = 0.2,   # adjust size of query cells based on # of cells
  saveplot = TRUE, 
  save_folder = 'projectionFigures/'
)

# show plots together with patchwork. Can also just call "projection_plots" object to display one-by-one
patchwork::wrap_plots(projection_plots, ncol = 2)

We can also set Hierarchy_only = TRUE to remove T/NK/Plasma/Stromal cells and focus solely on the hematopoietic hierarchy.

# Set batch/condition to be visualized individually
batch_key <- 'sampleID'

# returns a list of plots for each donor from a pre-specified batch variable
projection_plots <- plot_Projection_byDonor(
  query_obj = query, 
  batch_key = batch_key, 
  ref_obj = ref, 
  Hierarchy_only = TRUE, # Whether to exclude T/NK/Plasma/Stromal cells 
  downsample_reference = TRUE, 
  downsample_frac = 0.25,   # down-sample reference cells to 25%; reduces figure file size
  query_point_size = 0.2,   # adjust size of query cells based on # of cells
  saveplot = TRUE, 
  save_folder = 'projectionFigures/'
)

# show plots together with patchwork. Can also just call "projection_plots" object to display one-by-one
patchwork::wrap_plots(projection_plots, ncol = 2)

Note how cell composition differs between CD34p sorted Adult Bone Marrow and Fetal Bone Marrow within the Roy 2021 dataset. Notably, Adult bone marrow is enriched for more early HSC/MPP and LMPP cells and also MEP & Early Erythroid lineages. In contrast, Fetal bone marrow is highly enriched for MLP and B cell progenitors.

We can also compare the mapping of these samples along Hematopoietic pseudotime:

Get Composition data for each donor

Here, to study the abundance of each cell type within each donor, I focus on cells that were classified with a KNN prob > 0.5 (that is, >50% of nearest neighbours from the reference map agree on the assigned cell type).

We can present this as a long table

query_composition <- get_Composition(
  query_obj = query, 
  donor_key = 'sampleID', 
  celltype_label = 'predicted_CellType', 
  mapQC_col = 'mapping_error_QC', 
  knn_prob_cutoff = NULL, 
  return_type = 'long')

query_composition 

Or as a wide table with counts of # of cells

query_composition <- get_Composition(
  query_obj = query, 
  donor_key = 'sampleID', 
  celltype_label = 'predicted_CellType', 
  mapQC_col = 'mapping_error_QC', 
  knn_prob_cutoff = NULL, 
  return_type = 'count')

query_composition 

Or as a wide table with proportion of each cell type within each donor

query_composition <- get_Composition(
  query_obj = query, 
  donor_key = 'sampleID', 
  celltype_label = 'predicted_CellType', 
  mapQC_col = 'mapping_error_QC', 
  knn_prob_cutoff = NULL, 
  return_type = 'proportion')

query_composition 
# Simple heatmap to visualize composition of projected samples
p <- query_composition %>% column_to_rownames('sampleID') %>% data.matrix() %>% ComplexHeatmap::Heatmap()
p

LS0tCnRpdGxlOiAiQm9uZSBNYXJyb3cgUmVmZXJlbmNlIE1hcCBQcm9qZWN0aW9uIFR1dG9yaWFsIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoKSGVyZSB3ZSB3aWxsIG1hcCBhbiBleGFtcGxlIGRhdGFzZXQgZnJvbSBSb3kgZXQgYWwgKENlbGwgUmVwIDIwMTkpIGNvbXByaXNpbmcgMTIsMjQ1IENEMzQrIGNlbGxzIGZyb20gZmV0YWwgYW5kIGFkdWx0IGJvbmUgbWFycm93LgpOb3RlIHRoYXQgYW55IGh1bWFuIGhlbWF0b3BvaWV0aWMgY2VsbHMsIHJlZ2FyZGxlc3Mgb2YgdGlzc3VlIHNvdXJjZSBvciBkaXNlYXNlIHN0YXR1cywgY2FuIGJlIG1hcHBlZCB0byB0aGlzIHJlZmVyZW5jZS4KVGhpcyB0dXRvcmlhbCB3aWxsIHRha2UgYXBwcm94aW1hdGVseSAxMCBtaW51dGVzIHRvIHJ1bi4KCiMjIyBTZXR1cAoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzeW1waG9ueSkKbGlicmFyeShnZ3B1YnIpCmxpYnJhcnkocGF0Y2h3b3JrKQpgYGAKCkluc3RhbGwgcGFja2FnZSBmcm9tIGdpdGh1YgoKYGBge3J9CiMjIGluc3RhbGwgZGVwZW5kZW5jaWVzIHRoYXQgYXJlIG5vdCBvbiBDUkFOCmlmKCFyZXF1aXJlKEJpb2NNYW5hZ2VyLCBxdWlldGx5ID0gVFJVRSkpIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKQmlvY01hbmFnZXI6Omluc3RhbGwoYygiQVVDZWxsIiwgImRvTUMiKSkKaWYoIXJlcXVpcmUoZGV2dG9vbHMsIHF1aWV0bHkgPSBUUlVFKSkgaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImphcmVkaHVsaW5nL2pjb2xvcnMiKQpgYGAKIApgYGB7cn0KIyMgaW5zdGFsbCBCb25lTWFycm93TWFwIHBhY2thZ2UKZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCdhbmR5Z3h6ZW5nL0JvbmVNYXJyb3dNYXAnLCBmb3JjZSA9IFRSVUUpCmxpYnJhcnkoQm9uZU1hcnJvd01hcCkKYGBgCgogCiMjIyMgRG93bmxvYWQgcmVmZXJlbmNlIG9iamVjdCBhbmQgVU1BUCBtb2RlbAoKU2V0IHByb2plY3Rpb24gZm9sZGVyLCBEb3dubG9hZCByZWZlcmVuY2Ugb2JqZWN0IGFuZCBVTUFQIG1vZGVsCgpgYGB7cn0KIyBTZXQgZGlyZWN0b3J5IHRvIHN0b3JlIHByb2plY3Rpb24gcmVmZXJlbmNlIGZpbGVzCnByb2plY3Rpb25fcGF0aCA9ICcuLycKCiMgRG93bmxvYWQgQm9uZSBNYXJyb3cgUmVmZXJlbmNlIC0gMzQ0IE1iCmN1cmw6OmN1cmxfZG93bmxvYWQoJ2h0dHBzOi8vYm9uZW1hcnJvd21hcC5zMy51cy1lYXN0LTIuYW1hem9uYXdzLmNvbS9Cb25lTWFycm93X1JlZk1hcF9TeW1waG9ueVJlZi5yZHMnLCAKICAgICAgICAgICAgICAgICAgICBkZXN0ZmlsZSA9IHBhc3RlMChwcm9qZWN0aW9uX3BhdGgsICdCb25lTWFycm93X1JlZk1hcF9TeW1waG9ueVJlZi5yZHMnKSkKIyBEb3dubG9hZCB1d290IG1vZGVsIGZpbGUgLSAyMjEgTWIKY3VybDo6Y3VybF9kb3dubG9hZCgnaHR0cHM6Ly9ib25lbWFycm93bWFwLnMzLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tL0JvbmVNYXJyb3dfUmVmTWFwX3V3b3RfbW9kZWwudXdvdCcsIAogICAgICAgICAgICAgICAgICAgIGRlc3RmaWxlID0gcGFzdGUwKHByb2plY3Rpb25fcGF0aCwgJ0JvbmVNYXJyb3dfUmVmTWFwX3V3b3RfbW9kZWwudXdvdCcpKQoKIyBMb2FkIFN5bXBob255IHJlZmVyZW5jZQpyZWYgPC0gcmVhZFJEUyhwYXN0ZTAocHJvamVjdGlvbl9wYXRoLCAnQm9uZU1hcnJvd19SZWZNYXBfU3ltcGhvbnlSZWYucmRzJykpCiMgU2V0IHV3b3QgcGF0aCBmb3IgVU1BUCBwcm9qZWN0aW9uCnJlZiRzYXZlX3V3b3RfcGF0aCA8LSBwYXN0ZTAocHJvamVjdGlvbl9wYXRoLCAnQm9uZU1hcnJvd19SZWZNYXBfdXdvdF9tb2RlbC51d290JykKYGBgCgoKIyMjIyBWaXN1YWxpemUgQm9uZSBNYXJyb3cgUmVmZXJlbmNlCgpJZiB3ZSB3YW50IHRvIHZpc3VhbGl6ZSBjZWxsdHlwZSBsYWJlbHMgb3IgbWV0YWRhdGEgZnJvbSB0aGUgQk0gUmVmZXJlbmNlLCB3ZSBjYW4gY3JlYXRlIGEgU2V1cmF0IE9iamVjdCBmcm9tIHRoZSBzeW1waG9ueSByZWZlcmVuY2UgClRoaXMgd2lsbCBiZSBtZW1vcnkgZWZmaWNpZW50IGFzIGl0IHdpbGwgbm90IGluY2x1ZGUgZ2VuZSBleHByZXNzaW9uIGNvdW50cywgb25seSB0aGUgVU1BUCBjb29yZGluYXRlcyBhbmQgdGhlIG1ldGFkYXRhIGluY2x1ZGluZyBjZWxsIGxhYmVscyBhbmQgc29ydGluZyBpbmZvcm1hdGlvbgoKYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTExfQpSZWZlcmVuY2VTZXVyYXRPYmogPC0gY3JlYXRlX1JlZmVyZW5jZU9iamVjdChyZWYpCgpEaW1QbG90KFJlZmVyZW5jZVNldXJhdE9iaiwgcmVkdWN0aW9uID0gJ3VtYXAnLCBncm91cC5ieSA9ICdDZWxsVHlwZV9Bbm5vdGF0aW9uX2Zvcm1hdHRlZCcsIAogICAgICAgIHJhc3Rlcj1GQUxTRSwgbGFiZWw9VFJVRSwgbGFiZWwuc2l6ZSA9IDQpCmBgYAoKV2UgY2FuIHZpc3VhbGl6ZSBvdGhlciBhbm5vdGF0aW9ucyB0b28sIGluY2x1ZGluZyBjZWxsIGN5Y2xlIHBoYXNlIGFuZCBsaW5lYWdlIHBzZXVkb3RpbWUgZXN0aW1hdGVzLgoKYGBge3IsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9MTF9CnAxIDwtIERpbVBsb3QoUmVmZXJlbmNlU2V1cmF0T2JqLCByZWR1Y3Rpb24gPSAndW1hcCcsIGdyb3VwLmJ5ID0gJ0N5Y2xlUGhhc2UnLCByYXN0ZXI9RkFMU0UpCnAyIDwtIEZlYXR1cmVQbG90KFJlZmVyZW5jZVNldXJhdE9iaiwgcmVkdWN0aW9uID0gJ3VtYXAnLCBmZWF0dXJlcyA9ICdQc2V1ZG90aW1lJywgcmFzdGVyPUZBTFNFKSAKCnAxICsgcDIKYGBgCgoKIyMjIyBMb2FkIFF1ZXJ5IFNldXJhdCBvYmplY3QKRXhhbXBsZSBRdWVyeSBvYmplY3QgaXMgYSBzdWJzZXQgb2YgZGF0YSBmcm9tIFJveSBldCBhbCAoQ2VsbCBSZXBvcnRzIDIwMjEpLCBpbmNvcnBvcmF0aW5nIENEMzRwIGNlbGxzIGZyb20gYW4gYWR1bHQgYm9uZSBtYXJyb3cgc2FtcGxlIGFuZCBhIGZldGFsIGJvbmUgbWFycm93IHNhbXBsZS4gSGVyZSwgd2UgcHJlZmVyIHJhdyBjb3VudCBkYXRhIHdpdGhvdXQgbm9ybWFsaXphdGlvbi4KCklucHV0IGRvZXMgbm90IG5lZWQgdG8gYmUgYSBzZXVyYXQgb2JqZWN0LCBjYW4gbG9hZCByYXcgY291bnQgbWF0cml4IGFuZCBtZXRhZGF0YSBzZXBhcmF0ZWx5CgpgYGB7cn0KIyBMb2FkIGV4YW1wbGUgZGF0YSBmcm9tIFJveSBldCBhbCAtIDE0MSBNYgpjdXJsOjpjdXJsX2Rvd25sb2FkKCdodHRwczovL2JvbmVtYXJyb3dtYXAuczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vRXhhbXBsZVF1ZXJ5X1JveTIwMjEucmRzJywKICAgICAgICAgICAgICAgICAgICBkZXN0ZmlsZSA9IHBhc3RlMChwcm9qZWN0aW9uX3BhdGgsICdFeGFtcGxlUXVlcnlfUm95MjAyMS5yZHMnKSkKCiMgTG9hZCBzZXVyYXQgb2JqZWN0CnF1ZXJ5IDwtIHJlYWRSRFMocGFzdGUwKHByb2plY3Rpb25fcGF0aCwgJ0V4YW1wbGVRdWVyeV9Sb3kyMDIxLnJkcycpKQpxdWVyeQpgYGAKCiMjIyBNYXAgdGhlIFF1ZXJ5IERhdGEKUHJvdmlkZSByYXcgY291bnRzLCBtZXRhZGF0YSwgYW5kIGRvbm9yIGtleS4gVGhpcyBzaG91bGQgdGFrZSA8MSBtaW4KQ2FsY3VsYXRlIG1hcHBpbmcgZXJyb3IgYW5kIHBlcmZvcm0gUUMgdG8gcmVtb3ZlIGxvdyBxdWFsaXR5IGNlbGxzIHdpdGggaGlnaCBtYXBwaW5nIGVycm9yCgpgYGB7cn0KIyBiYXRjaCB2YXJpYWJsZSB0byBjb3JyZWN0IGluIHRoZSBxdWVyeSBkYXRhLCBzZXQgYXMgTlVMTCBpZiBubyBiYXRjaGVzIGluIHF1ZXJ5CmJhdGNodmFyIDwtICdzYW1wbGVJRCcKCiMgTWFwIHF1ZXJ5IGRhdGFzZXQgdXNpbmcgU3ltcGhvbnkgKEthbmcgZXQgYWwgMjAyMSkKcXVlcnkgPC0gbWFwX1F1ZXJ5KAogICAgZXhwX3F1ZXJ5ID0gcXVlcnlAYXNzYXlzJFJOQUBjb3VudHMsIAogICAgbWV0YWRhdGFfcXVlcnkgPSBxdWVyeUBtZXRhLmRhdGEsCiAgICByZWZfb2JqID0gcmVmLAogICAgdmFycyA9IGJhdGNodmFyCikKYGBgCgpOb3cgdGhhdCB0aGUgZGF0YSBpcyBtYXBwZWQsIHdlIHdpbGwgZXZhbHVhdGUgdGhlIG1hcHBpbmcgUUMgbWV0cmljcyBhbmQgZmxhZyBjZWxscyB3aXRoIGhpZ2ggbWFwcGluZyBlcnJvcnMKCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD0xMH0KIyBSdW4gUUMgYmFzZWQgb24gbWFwcGluZyBlcnJvciBzY29yZSwgZmxhZyBjZWxscyB3aXRoIG1hcHBpbmcgZXJyb3IgPj0gMi41IE1BRHMgYWJvdmUgbWVkaWFuCnF1ZXJ5IDwtIHF1ZXJ5ICU+JSBjYWxjdWxhdGVfTWFwcGluZ0Vycm9yKC4sIHJlZmVyZW5jZSA9IHJlZiwgTUFEX3RocmVzaG9sZCA9IDIuNSkgCgojIEdldCBRQyBQbG90cwpRQ19wbG90cyA8LSBwbG90X01hcHBpbmdFcnJvclFDKHF1ZXJ5KQoKIyBQbG90IHRvZ2V0aGVyIC0gSWYgdGhpcyBpcyB0b28gY3Jvd2RlZCwgY2FuIGFsc28ganVzdCBjYWxsICJRQ19wbG90cyIgYWxvbmV0byBkaXNwbGF5IG9uZSBieSBvbmUKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKFFDX3Bsb3RzLCBuY29sID0gNCwgd2lkdGhzID0gYygwLjgsIDAuMywgMC44LCAwLjMpKQpgYGAKCgpUaGlzIGltcG9ydGFudCBzdGVwIGlkZW50aWZpZXMgYSBzdWJzZXQgb2YgY2VsbHMgd2l0aCBoaWdoIG1hcHBpbmcgZXJyb3IgZnJvbSB0aGUgcXVlcnkgZGF0YXNldCB0aGF0IGFyZSBlaXRoZXI6CgoqIG5vdCBwcmVzZW50IHdpdGhpbiB0aGUgcmVmZXJlbmNlLCBvcgoqIGhhdmUgcG9vciBRQyBtZXRyaWNzIChsb3cgUk5BIGNvdW50cyBhbmQgbG93IHRyYW5zY3JpcHRpb25hbCBkaXZlcnNpdHkpCgpTb21ldGltZXMsIGxvdyBxdWFsaXR5IGNlbGxzIG1heSBlcnJvbmVvdXNseSBtYXAgdG8gdGhlIG9ydGhvY2hyb21hdGljIGVyeXRocm9ibGFzdCByZWdpb24gYXMgdGhpcyBjZWxsIHR5cGUgaGFzIHZlcnkgbG93IHRyYW5zY3JpcHRpb25hbCBkaXZlcnNpdHkuIApUaGVzZSBsb3cgcXVhbGl0eSBxdWVyeSBjZWxscyBkbyBub3QgaGF2ZSBoZW1vZ2xvYmluIGV4cHJlc3Npb24gYW5kIGFyZSBpbiBmYWN0IG1pcy1tYXBwZWQ7IHRoZXkgd2lsbCBiZSBmbGFnZ2VkIGJ5IHRoZSBRQyBmaWx0ZXIgYW5kIGV4Y2x1ZGVkIGZyb20gY2VsbCB0eXBlIGFzc2lnbm1lbnRzLgoKKipQbGVhc2UgYWRqdXN0IHRoZSBNQURfdGhyZXNob2xkICh0eXBpY2FsbHkgYmV0d2VlbiAxIGFuZCAzKSBiYXNlZCBvbiB0aGUgZGlzdHJpYnV0aW9uIG9mIHlvdXIgZGF0YXNldCB0byBpZGVudGlmeSB0aGUgb3V0bGllcnMgd2l0aCBsb3cgcXVhbGl0eSBhbmQgaGlnaCBtYXBwaW5nIGVycm9yIHNjb3Jlcy4gVGhpcyB3aWxsIGltcHJvdmUgeW91ciBjbGFzc2lmaWNhdGlvbnMgYW5kIGFueSBkb3duc3RyZWFtIGNvbXBvc2l0aW9uIGFuYWx5c2lzKioKCmBgYHtyfQojICMgT3B0aW9uYWwgc3RlcCAtIHJlbW92ZSBvdXRsaWVycyB3aXRoIGhpZ2ggbWFwcGluZyBlcnJvcgojIHF1ZXJ5IDwtIHN1YnNldChxdWVyeSwgbWFwcGluZ19lcnJvcl9RQyA9PSAnUGFzcycpCmBgYAoKT3B0aW9uYWxseSwgb3V0bGllciBjZWxscyB3aXRoIGhpZ2ggbWFwcGluZyBlcnJvciBjYW4gYWxzbyBiZSByZW1vdmVkIGF0IHRoaXMgc3RhZ2UuCkZvciBlYXNlIG9mIGludGVncmF0aW5nIHRoZXNlIG1hcHBlZCBhbm5vdGF0aW9ucyB3aXRoIHRoZSByZXN0IG9mIHlvdXIgYW5hbHlzaXMsIHdlIGFsc28gY2hvb3NlIHRvIHNraXAgdGhpcyBzdGVwLiBJZiBzbywgRmluYWwgQ2VsbFR5cGUgYW5kIFBzZXVkb3RpbWUgcHJlZGljdGlvbnMgd2lsbCBiZSBhc3NpZ25lZCBhcyBOQSBmb3IgY2VsbHMgZmFpbGluZyB0aGUgbWFwcGluZyBlcnJvciBRQyB0aHJlc2hvbGQuIAoKCiMjIyBDZWxsIFR5cGUgQXNzaWdubWVudHMKV2Ugd2lsbCBuZXh0IHVzZSBhIEtOTiBjbGFzc2lmaWVyIHRvIGFzc2lnbiBjZWxsIGlkZW50aXR5IGJhc2VkIG9uIHRoZSAzMCBLLU5lYXJlc3QgTmVpZ2hib3VycyBmcm9tIHRoZSByZWZlcmVuY2UgbWFwLgpUaGlzIGxhYmVsIHRyYW5zZmVyIHN0ZXAgd2lsbCB0YWtlIGxvbmdlciwgcG90ZW50aWFsbHkgYXJvdW5kIDEwIG1pbnV0ZXMgZm9yIH4xMCwwMDAgY2VsbHMgCgpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9CiMgUHJlZGljdCBIZW1hdG9wb2lldGljIENlbGwgVHlwZXMgYnkgS05OIGNsYXNzaWZpY2F0aW9uCnF1ZXJ5IDwtIHByZWRpY3RfQ2VsbFR5cGVzKAogIHF1ZXJ5X29iaiA9IHF1ZXJ5LCAKICByZWZfb2JqID0gcmVmLCAKICBpbml0aWFsX2xhYmVsID0gJ2luaXRpYWxfQ2VsbFR5cGUnLCAjIGNlbGx0eXBlIGFzc2lnbm1lbnRzIGJlZm9yZSBmaWx0ZXJpbmcgb24gbWFwcGluZyBRQwogIGZpbmFsX2xhYmVsID0gJ3ByZWRpY3RlZF9DZWxsVHlwZScgICMgY2VsbHR5cGUgYXNzaWdubWVudHMgd2l0aCBtYXAgUUMgZmFpbGluZyBjZWxscyBhc3NpZ25lZCBhcyBOQQopIAoKRGltUGxvdChzdWJzZXQocXVlcnksIG1hcHBpbmdfZXJyb3JfUUMgPT0gJ1Bhc3MnKSwgcmVkdWN0aW9uID0gJ3VtYXAnLCBncm91cC5ieSA9IGMoJ3ByZWRpY3RlZF9DZWxsVHlwZScpLCByYXN0ZXI9RkFMU0UsIGxhYmVsPVRSVUUsIGxhYmVsLnNpemUgPSA0KQpgYGAKCgojIyMjIFBzZXVkb3RpbWUgQW5ub3RhdGlvbnMKV2UgY2FuIGFsc28gYW5ub3RhdGUgZWFjaCBxdWVyeSBjZWxsIGJhc2VkIG9uIHRoZWlyIHBvc2l0aW9uIGFsb25nIGhlbWF0b3BvaWV0aWMgcHNldWRvdGltZS4gClF1ZXJ5IGNlbGxzIHdpbGwgYmUgYXNzaWduZWQgYSBwc2V1ZG90aW1lIHNjb3JlIGJhc2VkIG9uIHRoZSAzMCBLLU5lYXJlc3QgTmVpZ2hib3VycyBmcm9tIHRoZSByZWZlcmVuY2UgbWFwLgpTaW5jZSBvdXIgUHNldWRvdGltZSBLTk4gYXNzaWdubWVudHMgYXJlIHBlcmZvcm1lZCBpbiBVTUFQIHNwYWNlIChtb3JlIGFjY3VyYXRlIHRoYW4gS05OIG9uIGhhcm1vbnkgY29tcG9uZW50cyksIHRoaXMgc3RlcCBpcyB2ZXJ5IGZhc3QgKDwgMTBzKQoKYGBge3J9CiMgUHJlZGljdCBQc2V1ZG90aW1lIHZhbHVlcyBieSBLTk4KcXVlcnkgPC0gcHJlZGljdF9Qc2V1ZG90aW1lKAogIHF1ZXJ5X29iaiA9IHF1ZXJ5LCAKICByZWZfb2JqID0gcmVmLCAKICBpbml0aWFsX2xhYmVsID0gJ2luaXRpYWxfUHNldWRvdGltZScsICAjIHBzZXVkb3RpbWUgYXNzaWdubWVudHMgYmVmb3JlIGZpbHRlcmluZyBvbiBtYXBwaW5nIFFDCiAgZmluYWxfbGFiZWwgPSAncHJlZGljdGVkX1BzZXVkb3RpbWUnICAgIyBwc2V1ZG90aW1lIGFzc2lnbm1lbnRzIHdpdGggbWFwIFFDIGZhaWxpbmcgY2VsbHMgYXNzaWduZWQgYXMgTkEKKQoKIyBWaXN1YWxpemUgSGVtYXRvcG9pZXRpYyBQc2V1ZG90aW1lIGluIHF1ZXJ5IGRhdGEKRmVhdHVyZVBsb3Qoc3Vic2V0KHF1ZXJ5LCBtYXBwaW5nX2Vycm9yX1FDID09ICdQYXNzJyksIGZlYXR1cmVzID0gYygncHJlZGljdGVkX1BzZXVkb3RpbWUnKSkKYGBgCgoKIyMjIFNhdmUgcHJvamVjdGlvbiByZXN1bHRzCgpUaGlzIHdpbGwgc2F2ZSBhIGNzdiBmaWxlIHdpdGggdGhlIG1hcHBlZCBhbm5vdGF0aW9ucyBmb3IgZWFjaCBjZWxsIChtYXBwaW5nIGVycm9yIHNjb3JlcywgdW1hcCBjb29yZGluYXRlcywgcHJlZGljdGVkIENlbGwgVHlwZSwgYW5kIHByZWRpY3RlZCBQc2V1ZG90aW1lKQoKYGBge3J9CiMgU2F2ZSBDZWxsVHlwZSBBbm5vdGF0aW9ucyBhbmQgUHJvamVjdGVkIFVNQVAgY29vcmRpbmF0ZXMKc2F2ZV9Qcm9qZWN0aW9uUmVzdWx0cygKICBxdWVyeV9vYmogPSBxdWVyeSwgCiAgY2VsbHR5cGVfbGFiZWwgPSAncHJlZGljdGVkX0NlbGxUeXBlJywgCiAgY2VsbHR5cGVfS05OcHJvYl9sYWJlbCA9ICdwcmVkaWN0ZWRfQ2VsbFR5cGVfcHJvYicsIAogIHBzZXVkb3RpbWVfbGFiZWwgPSAncHJlZGljdGVkX1BzZXVkb3RpbWUnLCAKICBmaWxlX25hbWUgPSAncXVlcnlkYXRhX3Byb2plY3RlZF9sYWJlbGVkLmNzdicpCmBgYAoKCiMjIyBWaXN1YWxpemUgUHJvamVjdGlvbiBEZW5zaXR5CgpOb3cgbGV0J3MgdmlzdWFsaXplIHRoZSBkZW5zaXR5IGRpc3RyaWJ1dGlvbiBvZiBxdWVyeSBjZWxscyBhY3Jvc3MgdGhlIGhlbWF0b3BvaWV0aWMgaGllcmFyY2h5CgpgYGB7ciwgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9OX0KIyBTZXQgYmF0Y2gvY29uZGl0aW9uIHRvIGJlIHZpc3VhbGl6ZWQgaW5kaXZpZHVhbGx5CmJhdGNoX2tleSA8LSAnc2FtcGxlSUQnCgojIHJldHVybnMgYSBsaXN0IG9mIHBsb3RzIGZvciBlYWNoIGRvbm9yIGZyb20gYSBwcmUtc3BlY2lmaWVkIGJhdGNoIHZhcmlhYmxlCnByb2plY3Rpb25fcGxvdHMgPC0gcGxvdF9Qcm9qZWN0aW9uX2J5RG9ub3IoCiAgcXVlcnlfb2JqID0gcXVlcnksIAogIGJhdGNoX2tleSA9IGJhdGNoX2tleSwgCiAgcmVmX29iaiA9IHJlZiwgCiAgSGllcmFyY2h5X29ubHkgPSBGQUxTRSwgIyBXaGV0aGVyIHRvIGV4Y2x1ZGUgVC9OSy9QbGFzbWEvU3Ryb21hbCBjZWxscyAKICBkb3duc2FtcGxlX3JlZmVyZW5jZSA9IFRSVUUsIAogIGRvd25zYW1wbGVfZnJhYyA9IDAuMjUsICAgIyBkb3duLXNhbXBsZSByZWZlcmVuY2UgY2VsbHMgdG8gMjUlOyByZWR1Y2VzIGZpZ3VyZSBmaWxlIHNpemUKICBxdWVyeV9wb2ludF9zaXplID0gMC4yLCAgICMgYWRqdXN0IHNpemUgb2YgcXVlcnkgY2VsbHMgYmFzZWQgb24gIyBvZiBjZWxscwogIHNhdmVwbG90ID0gVFJVRSwgCiAgc2F2ZV9mb2xkZXIgPSAncHJvamVjdGlvbkZpZ3VyZXMvJwopCgojIHNob3cgcGxvdHMgdG9nZXRoZXIgd2l0aCBwYXRjaHdvcmsuIENhbiBhbHNvIGp1c3QgY2FsbCAicHJvamVjdGlvbl9wbG90cyIgb2JqZWN0IHRvIGRpc3BsYXkgb25lLWJ5LW9uZQpwYXRjaHdvcms6OndyYXBfcGxvdHMocHJvamVjdGlvbl9wbG90cywgbmNvbCA9IDIpCmBgYAoKV2UgY2FuIGFsc28gc2V0IEhpZXJhcmNoeV9vbmx5ID0gVFJVRSB0byByZW1vdmUgVC9OSy9QbGFzbWEvU3Ryb21hbCBjZWxscyBhbmQgZm9jdXMgc29sZWx5IG9uIHRoZSBoZW1hdG9wb2lldGljIGhpZXJhcmNoeS4KCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD04fQojIFNldCBiYXRjaC9jb25kaXRpb24gdG8gYmUgdmlzdWFsaXplZCBpbmRpdmlkdWFsbHkKYmF0Y2hfa2V5IDwtICdzYW1wbGVJRCcKCiMgcmV0dXJucyBhIGxpc3Qgb2YgcGxvdHMgZm9yIGVhY2ggZG9ub3IgZnJvbSBhIHByZS1zcGVjaWZpZWQgYmF0Y2ggdmFyaWFibGUKcHJvamVjdGlvbl9wbG90cyA8LSBwbG90X1Byb2plY3Rpb25fYnlEb25vcigKICBxdWVyeV9vYmogPSBxdWVyeSwgCiAgYmF0Y2hfa2V5ID0gYmF0Y2hfa2V5LCAKICByZWZfb2JqID0gcmVmLCAKICBIaWVyYXJjaHlfb25seSA9IFRSVUUsICMgV2hldGhlciB0byBleGNsdWRlIFQvTksvUGxhc21hL1N0cm9tYWwgY2VsbHMgCiAgZG93bnNhbXBsZV9yZWZlcmVuY2UgPSBUUlVFLCAKICBkb3duc2FtcGxlX2ZyYWMgPSAwLjI1LCAgICMgZG93bi1zYW1wbGUgcmVmZXJlbmNlIGNlbGxzIHRvIDI1JTsgcmVkdWNlcyBmaWd1cmUgZmlsZSBzaXplCiAgcXVlcnlfcG9pbnRfc2l6ZSA9IDAuMiwgICAjIGFkanVzdCBzaXplIG9mIHF1ZXJ5IGNlbGxzIGJhc2VkIG9uICMgb2YgY2VsbHMKICBzYXZlcGxvdCA9IFRSVUUsIAogIHNhdmVfZm9sZGVyID0gJ3Byb2plY3Rpb25GaWd1cmVzLycKKQoKIyBzaG93IHBsb3RzIHRvZ2V0aGVyIHdpdGggcGF0Y2h3b3JrLiBDYW4gYWxzbyBqdXN0IGNhbGwgInByb2plY3Rpb25fcGxvdHMiIG9iamVjdCB0byBkaXNwbGF5IG9uZS1ieS1vbmUKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHByb2plY3Rpb25fcGxvdHMsIG5jb2wgPSAyKQpgYGAKCk5vdGUgaG93IGNlbGwgY29tcG9zaXRpb24gZGlmZmVycyBiZXR3ZWVuIENEMzRwIHNvcnRlZCBBZHVsdCBCb25lIE1hcnJvdyBhbmQgRmV0YWwgQm9uZSBNYXJyb3cgd2l0aGluIHRoZSBSb3kgMjAyMSBkYXRhc2V0LiAKTm90YWJseSwgQWR1bHQgYm9uZSBtYXJyb3cgaXMgZW5yaWNoZWQgZm9yIG1vcmUgZWFybHkgSFNDL01QUCBhbmQgTE1QUCBjZWxscyBhbmQgYWxzbyBNRVAgJiBFYXJseSBFcnl0aHJvaWQgbGluZWFnZXMuIApJbiBjb250cmFzdCwgRmV0YWwgYm9uZSBtYXJyb3cgaXMgaGlnaGx5IGVucmljaGVkIGZvciBNTFAgYW5kIEIgY2VsbCBwcm9nZW5pdG9ycy4gCgpXZSBjYW4gYWxzbyBjb21wYXJlIHRoZSBtYXBwaW5nIG9mIHRoZXNlIHNhbXBsZXMgYWxvbmcgSGVtYXRvcG9pZXRpYyBwc2V1ZG90aW1lOgoKCiMjIyBHZXQgQ29tcG9zaXRpb24gZGF0YSBmb3IgZWFjaCBkb25vcgoKSGVyZSwgdG8gc3R1ZHkgdGhlIGFidW5kYW5jZSBvZiBlYWNoIGNlbGwgdHlwZSB3aXRoaW4gZWFjaCBkb25vciwgSSBmb2N1cyBvbiBjZWxscyB0aGF0IHdlcmUgY2xhc3NpZmllZCB3aXRoIGEgS05OIHByb2IgPiAwLjUgKHRoYXQgaXMsID41MCUgb2YgbmVhcmVzdCBuZWlnaGJvdXJzIGZyb20gdGhlIHJlZmVyZW5jZSBtYXAgYWdyZWUgb24gdGhlIGFzc2lnbmVkIGNlbGwgdHlwZSkuIAoKV2UgY2FuIHByZXNlbnQgdGhpcyBhcyBhIGxvbmcgdGFibGUKCmBgYHtyfQpxdWVyeV9jb21wb3NpdGlvbiA8LSBnZXRfQ29tcG9zaXRpb24oCiAgcXVlcnlfb2JqID0gcXVlcnksIAogIGRvbm9yX2tleSA9ICdzYW1wbGVJRCcsIAogIGNlbGx0eXBlX2xhYmVsID0gJ3ByZWRpY3RlZF9DZWxsVHlwZScsIAogIG1hcFFDX2NvbCA9ICdtYXBwaW5nX2Vycm9yX1FDJywgCiAga25uX3Byb2JfY3V0b2ZmID0gTlVMTCwgCiAgcmV0dXJuX3R5cGUgPSAnbG9uZycpCgpxdWVyeV9jb21wb3NpdGlvbiAKYGBgCgpPciBhcyBhIHdpZGUgdGFibGUgd2l0aCBjb3VudHMgb2YgIyBvZiBjZWxscwoKYGBge3J9CnF1ZXJ5X2NvbXBvc2l0aW9uIDwtIGdldF9Db21wb3NpdGlvbigKICBxdWVyeV9vYmogPSBxdWVyeSwgCiAgZG9ub3Jfa2V5ID0gJ3NhbXBsZUlEJywgCiAgY2VsbHR5cGVfbGFiZWwgPSAncHJlZGljdGVkX0NlbGxUeXBlJywgCiAgbWFwUUNfY29sID0gJ21hcHBpbmdfZXJyb3JfUUMnLCAKICBrbm5fcHJvYl9jdXRvZmYgPSBOVUxMLCAKICByZXR1cm5fdHlwZSA9ICdjb3VudCcpCgpxdWVyeV9jb21wb3NpdGlvbiAKYGBgCgpPciBhcyBhIHdpZGUgdGFibGUgd2l0aCBwcm9wb3J0aW9uIG9mIGVhY2ggY2VsbCB0eXBlIHdpdGhpbiBlYWNoIGRvbm9yCgpgYGB7cn0KcXVlcnlfY29tcG9zaXRpb24gPC0gZ2V0X0NvbXBvc2l0aW9uKAogIHF1ZXJ5X29iaiA9IHF1ZXJ5LCAKICBkb25vcl9rZXkgPSAnc2FtcGxlSUQnLCAKICBjZWxsdHlwZV9sYWJlbCA9ICdwcmVkaWN0ZWRfQ2VsbFR5cGUnLCAKICBtYXBRQ19jb2wgPSAnbWFwcGluZ19lcnJvcl9RQycsIAogIGtubl9wcm9iX2N1dG9mZiA9IE5VTEwsIAogIHJldHVybl90eXBlID0gJ3Byb3BvcnRpb24nKQoKcXVlcnlfY29tcG9zaXRpb24gCmBgYAoKCmBgYHtyfQojIFNpbXBsZSBoZWF0bWFwIHRvIHZpc3VhbGl6ZSBjb21wb3NpdGlvbiBvZiBwcm9qZWN0ZWQgc2FtcGxlcwpwIDwtIHF1ZXJ5X2NvbXBvc2l0aW9uICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoJ3NhbXBsZUlEJykgJT4lIGRhdGEubWF0cml4KCkgJT4lIENvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKCkKcApgYGAK